展开单行选择代码
选择单行 - 模拟白酒A:
股票名称 模拟白酒A
收盘价 1850.0
涨跌幅 0.05
成交量 1200
Name: SIM-A, dtype: object
返回类型: <class 'pandas.core.series.Series'>
lst-loc-indexing、lst-query-method 与布尔筛选,生成同一候选集。数据检索(Data Retrieval)是数据分析中最基础也最关键的操作之一。
在金融领域,典型应用场景包括:
通过本章学习,你将掌握以下核心技能:
loc)和基于位置(iloc)的两套索引体系一个二维数据框可以表示为矩阵 \(D \in \mathbb{R}^{n \times m}\):
\[ \large I_P=\{i\in\{1,\ldots,n\}\mid P(d_{i,:})=\text{True}\},\qquad D_{\text{filtered}}=D_{I_P,:} \]
Pandas提供了两套并行的索引系统:
| 特性 | loc(标签索引) |
iloc(位置索引) |
|---|---|---|
| 索引类型 | 数据标签(代码、日期等) | 整数位置(从0开始) |
| 切片行为 | 闭区间 [start, end] |
半开区间 [start, end) |
| 适用场景 | 语义查询(找特定股票) | 算法实现(取前N行) |
数据声明:以下四行价格、涨跌幅和成交量均为匿名课堂构造,不对应任何真实公司或具体交易日。
import pandas as pd
import numpy as np
# 创建匿名课堂行情示例
data = { # 定义四项不映射真实公司的课堂记录
'股票代码': ['SIM-A', 'SIM-B', 'SIM-C', 'SIM-D'], # 设置匿名模拟代码
'股票名称': ['模拟白酒A', '模拟白酒B', '模拟银行A', '模拟保险A'], # 设置匿名模拟名称并保留行业词示例
'收盘价': [1850.0, 220.5, 45.2, 52.8], # 设置课堂模拟收盘价
'涨跌幅': [0.05, -0.02, 0.03, -0.01], # 设置课堂模拟涨跌幅
'成交量': [1200, 3500, 8900, 5600] # 设置课堂模拟成交量
}
df = pd.DataFrame(data)
# 方法1:点号访问(列名必须是合法Python标识符)
names = df.股票名称
print('方法1 - 点号选择:')
print(f'类型: {type(names)}')
print(names)方法1 - 点号选择:
类型: <class 'pandas.core.series.Series'>
0 模拟白酒A
1 模拟白酒B
2 模拟银行A
3 模拟保险A
Name: 股票名称, dtype: object
Series(一维数据)DataFrame(二维表格)索引后的DataFrame:
股票名称 收盘价 涨跌幅 成交量
股票代码
SIM-A 模拟白酒A 1850.0 0.05 1200
SIM-B 模拟白酒B 220.5 -0.02 3500
SIM-C 模拟银行A 45.2 0.03 8900
SIM-D 模拟保险A 52.8 -0.01 5600
行索引: ['SIM-A', 'SIM-B', 'SIM-C', 'SIM-D']
loc切片包含结束位置(闭区间)行和列的组合选择:
股票名称 收盘价
股票代码
SIM-A 模拟白酒A 1850.0
SIM-B 模拟白酒B 220.5
单个标量值: 1850.0
类型: <class 'numpy.float64'>
df.at[row_label, col_label] 是专门为标量访问优化的方法,比loc更快iloc切片1:3(不包含索引3):
股票代码 股票名称 收盘价 涨跌幅 成交量
1 SIM-B 模拟白酒B 220.5 -0.02 3500
2 SIM-C 模拟银行A 45.2 0.03 8900
第0行第2列的值: 1850.0
最后一行:
股票代码 SIM-D
股票名称 模拟保险A
收盘价 52.8
涨跌幅 -0.01
成交量 5600
Name: 3, dtype: object
iloc的切片不包含结束位置(半开区间),与Python列表一致布尔条件(收盘价>100):
0 True
1 True
2 False
3 False
Name: 收盘价, dtype: bool
筛选结果(收盘价>100的股票):
股票代码 股票名称 收盘价 涨跌幅 成交量
0 SIM-A 模拟白酒A 1850.0 0.05 1200
1 SIM-B 模拟白酒B 220.5 -0.02 3500
# AND条件:价格>50 且 涨幅>0(必须用括号!)
condition_and = (df['收盘价'] > 50) & (df['涨跌幅'] > 0)
print('AND条件筛选(价格>50 且 涨幅>0):')
result_and = df[condition_and]
print(result_and[['股票名称', '收盘价', '涨跌幅']])
# OR条件:名称含"银行"或"保险"
condition_or = (df['股票名称'].str.contains('银行')) | (df['股票名称'].str.contains('保险'))
print('\n\nOR条件筛选(银行或保险):')
result_or = df[condition_or]
print(result_or[['股票名称', '收盘价']])AND条件筛选(价格>50 且 涨幅>0):
股票名称 收盘价 涨跌幅
0 模拟白酒A 1850.0 0.05
OR条件筛选(银行或保险):
股票名称 收盘价
2 模拟银行A 45.2
3 模拟保险A 52.8
| 运算符 | 含义 | 示例 |
|---|---|---|
& |
AND(同时满足) | (df['PE'] < 20) & (df['ROE'] > 0.15) |
\| |
OR(满足其一) | (df['行业'] == '银行') \| (df['行业'] == '保险') |
~ |
NOT(条件取反) | ~df['代码'].isin(blacklist) |
# 传统布尔索引方法
complex_condition = (
(df['收盘价'] > 40) &
(df['收盘价'] < 2000) &
(df['涨跌幅'] > 0) &
(df['成交量'] > 2000)
)
result_complex = df[complex_condition]
print('复杂条件筛选结果:')
print(result_complex[['股票名称', '收盘价', '涨跌幅', '成交量']])
# query方法:语法更简洁
result_query = df.query('收盘价 > 40 and 收盘价 < 2000 and 涨跌幅 > 0 and 成交量 > 2000')
print('\n使用query方法的等价结果:')
print(result_query[['股票名称', '收盘价', '涨跌幅', '成交量']])复杂条件筛选结果:
股票名称 收盘价 涨跌幅 成交量
2 模拟银行A 45.2 0.03 8900
使用query方法的等价结果:
股票名称 收盘价 涨跌幅 成交量
2 模拟银行A 45.2 0.03 8900
# 从股票池中筛选关注列表中的股票
watchlist = ['SIM-A', 'SIM-C'] # 设置匿名课堂关注列表
selected = df[df['股票代码'].isin(watchlist)]
print('筛选关注列表中的股票:')
print(selected[['股票代码', '股票名称', '收盘价']])
# 组合使用:关注列表中且上涨的股票
selected_and_rising = df[
df['股票代码'].isin(watchlist) &
(df['涨跌幅'] > 0)
]
print('\n关注列表中上涨的股票:')
print(selected_and_rising[['股票名称', '涨跌幅']])筛选关注列表中的股票:
股票代码 股票名称 收盘价
0 SIM-A 模拟白酒A 1850.0
2 SIM-C 模拟银行A 45.2
关注列表中上涨的股票:
股票名称 涨跌幅
0 模拟白酒A 0.05
2 模拟银行A 0.03
isin() 的哈希成员探测通常是均摊 \(O(1)\) / 元素,但整列检查仍约为 \(O(n+k)\)(\(n\) 为行数,\(k\) 为候选项数);是否更快需实测。~ 可与其他条件自由组合# 设置索引
df_with_index = df.set_index('股票代码')
# 重置索引(索引变回普通列)
df_reset = df_with_index.reset_index()
# 丢弃原索引的重置
df_reset_drop = df_with_index.reset_index(drop=True)
index_check = pd.Series({'设置后索引名称': df_with_index.index.name, '普通重置首列': df_reset.columns[0], '丢弃重置含股票代码': '股票代码' in df_reset_drop.columns, '三种结果行数一致': len(df_with_index) == len(df_reset) == len(df_reset_drop)}) # 汇总三种索引变换的便于核对结果
print('索引设置与重置核验:') # 标明输出对应索引结构变化
print(index_check) # 输出字段保留与行数一致性依据索引设置与重置核验:
设置后索引名称 股票代码
普通重置首列 股票代码
丢弃重置含股票代码 False
三种结果行数一致 True
dtype: object
import time
np.random.seed(42) # 固定随机种子使每次课堂性能示例一致
n_rows = 100000 # 设置匿名课堂数据规模
large_df = pd.DataFrame({'code': np.random.choice([f'SIM-{i:04d}' for i in range(1, 1001)], n_rows), 'price': np.random.uniform(10, 100, n_rows), 'volume': np.random.randint(1000, 10000, n_rows)}) # 构造十万行匿名课堂数据
query_codes = [f'SIM-{i:04d}' for i in range(451, 551)] # 设置一百个重复查询键
repeat_count = 5 # 设置每个查询键重复五轮
indexed_df = large_df.set_index('code') # 预先构建索引以评估重复查询阶段
start = time.perf_counter() # 记录布尔扫描计时起点
scan_results = [large_df[large_df['code'] == code] for _ in range(repeat_count) for code in query_codes] # 对相同键执行五百次布尔扫描
scan_time = time.perf_counter() - start # 计算匹配工作量的布尔扫描耗时
start = time.perf_counter() # 记录索引查询计时起点
index_results = [indexed_df.loc[[code]] for _ in range(repeat_count) for code in query_codes] # 对相同键执行五百次索引查询
index_time = time.perf_counter() - start # 计算匹配工作量的索引查询耗时
workload_count = len(query_codes) * repeat_count # 计算两种方法共同的查询次数
assert len(scan_results) == len(index_results) == workload_count # 验证两种方法执行完全相同的查询次数
assert all(len(scan_result) == len(index_result) for scan_result, index_result in zip(scan_results, index_results)) # 验证每次查询返回相同行数
benchmark_summary = pd.Series({'每种方法查询次数': workload_count, '布尔扫描耗时(ms)': scan_time * 1000, '索引查询耗时(ms)': index_time * 1000, '扫描/索引耗时比': scan_time / index_time}) # 汇总匹配工作量的示例计时
print(benchmark_summary.round(2)) # 输出当前运行环境的课堂基准结果每种方法查询次数 500.00
布尔扫描耗时(ms) 2891.89
索引查询耗时(ms) 1036.70
扫描/索引耗时比 2.79
dtype: float64
限制:计时不含索引构建成本,仅说明同一数据上重复查询阶段的本机示例;单次查询、不同索引类型或不同硬件不保证加速,更不能概括为端到端 \(O(1)\)。
# 不推荐:使用循环
start = time.time()
prices_loop = []
for i in range(len(df)):
if df.iloc[i]['收盘价'] > 100:
prices_loop.append(df.iloc[i]['收盘价'])
time_loop = time.time() - start
# 推荐:向量化操作
start = time.time()
prices_vectorized = df.loc[df['收盘价'] > 100, '收盘价']
time_vectorized = time.time() - start
print(f'循环方法耗时: {time_loop*1000:.2f}毫秒')
print(f'向量化方法耗时: {time_vectorized*1000:.2f}毫秒')循环方法耗时: 0.43毫秒
向量化方法耗时: 0.58毫秒
| 优化策略 | 原理 | 适用场景 |
|---|---|---|
使用.loc而非链式索引 |
避免不必要的内存复制 | 所有筛选+取列操作 |
| 设置索引加速查找 | 重复查询可避免每次全表扫描,但有构建与维护成本 | 同一键空间上的多次查找 |
| 向量化替代循环 | C/Fortran底层优化 | 所有批量数据操作 |
使用.at标量访问 |
专为单值优化 | 只需取一个值 |
数据声明:以下 1000 行估值与财务指标均由固定种子生成,仅用于演示检索流程,不是 A 股实证样本或投资建议。
import pandas as pd
import numpy as np
# 生成1000行匿名课堂候选数据
np.random.seed(42) # 固定随机种子以保证每次筛选结果一致
n_stocks = 1000 # 设置课堂模拟候选行数
stocks_data = pd.DataFrame({
'股票代码': [f'SIM-{i:04d}' for i in range(1, n_stocks + 1)], # 生成不映射真实证券的匿名代码
'股票名称': [f'股票{i}' for i in range(1, n_stocks+1)],
'市盈率': np.random.uniform(5, 80, n_stocks),
'市净率': np.random.uniform(0.5, 10, n_stocks),
'ROE': np.random.uniform(0.05, 0.35, n_stocks),
'负债率': np.random.uniform(0.2, 0.9, n_stocks),
'股息率': np.random.uniform(0.005, 0.08, n_stocks)
})
print(f'课堂模拟候选集: {len(stocks_data)}行') # 标明输出不是市场股票池
print('\n课堂生成指标概况:') # 标明描述统计来自生成数据
print(stocks_data[['市盈率', '市净率', 'ROE', '负债率', '股息率']].describe())课堂模拟候选集: 1000行
课堂生成指标概况:
市盈率 市净率 ROE 负债率 股息率
count 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
mean 41.769241 5.316664 0.200722 0.543263 0.042058
std 21.910302 2.775804 0.087202 0.200543 0.021511
min 5.347402 0.530574 0.050003 0.200457 0.005002
25% 22.697995 2.790206 0.128405 0.369331 0.023373
50% 42.260553 5.427972 0.200184 0.538995 0.042095
75% 60.823969 7.724418 0.277731 0.716279 0.060500
max 79.978825 9.994430 0.349346 0.899690 0.079831
课堂启发式阈值:PE<20、PB<3、ROE>15%、负债率<60%、股息率>2% 仅用于演示布尔检索,不是跨行业、跨时期通用标准。
# 筛选条件:PE<20, PB<3, ROE>15%, 负债率<60%, 股息率>2%
candidate_criteria = ( # 组合五项课堂启发式筛选条件
(stocks_data['市盈率'] < 20) &
(stocks_data['市净率'] < 3) &
(stocks_data['ROE'] > 0.15) &
(stocks_data['负债率'] < 0.6) &
(stocks_data['股息率'] > 0.02)
)
candidate_stocks = stocks_data[candidate_criteria].copy() # 生成课堂候选清单而非投资组合
# 按ROE降序展示前三项课堂候选
candidate_stocks_sorted = candidate_stocks.sort_values('ROE', ascending=False) # 按课堂模拟ROE排序候选清单
candidate_count = len(candidate_stocks) # 计算固定种子下的候选数量
candidate_rate = candidate_count / len(stocks_data) * 100 # 计算候选通过率百分比
assert candidate_count == 13 # 验证固定种子结果为13项候选
assert np.isclose(candidate_rate, 1.3) # 验证13除以1000对应1.3%的通过率
screen_summary = pd.Series({'候选数量': candidate_count, '通过率(%)': candidate_rate}) # 汇总候选规模与通过率
top_value_stocks = candidate_stocks_sorted[['股票代码', '市盈率', '市净率', 'ROE', '负债率', '股息率']].head(3).round(2) # 保留ROE最高的三项课堂候选
print('课堂候选筛选摘要:') # 标明可见输出为候选清单核验
print(screen_summary.round(2)) # 输出筛选数量与通过率
print('\nROE最高的三项课堂候选:') # 标明排序摘要的范围
print(top_value_stocks) # 输出Top 3课堂候选的关键指标课堂候选筛选摘要:
候选数量 13.0
通过率(%) 1.3
dtype: float64
ROE最高的三项课堂候选:
股票代码 市盈率 市净率 ROE 负债率 股息率
576 SIM-0577 16.03 0.72 0.34 0.34 0.04
100 SIM-0101 7.36 1.05 0.32 0.26 0.05
925 SIM-0926 6.69 1.38 0.32 0.22 0.02
print('课堂候选清单统计特征:') # 标明输出不是已构建投资组合
print(f'平均市盈率: {candidate_stocks["市盈率"].mean():.2f}') # 输出候选清单平均市盈率
print(f'平均市净率: {candidate_stocks["市净率"].mean():.2f}') # 输出候选清单平均市净率
print(f'平均ROE: {candidate_stocks["ROE"].mean():.2%}') # 输出候选清单平均模拟ROE
print(f'平均股息率: {candidate_stocks["股息率"].mean():.2%}') # 输出候选清单平均模拟股息率课堂候选清单统计特征:
平均市盈率: 12.71
平均市净率: 1.45
平均ROE: 25.41%
平均股息率: 4.34%
price_SHSC 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务一
import pandas as pd
price_SHSC = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/1726305586753.xlsx") #导入数据
price_SHSC["日期"] = pd.to_datetime(price_SHSC["日期"] , format='%Y%m%d') # 转换为日期时间格式
price_SHSC.set_index("日期",inplace=True) # 将日期列设为price_SHSC数据框的索引
print(price_SHSC.index) #查看数据框的行索引
print(price_SHSC.columns) #查看数据框的列名
print(price_SHSC.shape) #看看数据框的形状参数
#任务二
import pandas as pd
price_SHSC = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/1726305586753.xlsx") #导入数据
price_SHSC["日期"] = pd.to_datetime(price_SHSC["日期"] , format='%Y%m%d') # 转换为日期时间格式
price_SHSC.set_index("日期",inplace=True) # 将日期列设为price_SHSC数据框的索引
print(price_SHSC.loc["2024-04-08"]) #查看2024年4月8日的收盘价数据
print(price_SHSC.loc["2024-05-16"]) #查看2024年5月16日的收盘价数据
print(price_SHSC.loc["2024-06-11"]) #查看2024年6月11日的收盘价数据
print(price_SHSC.loc["2024-06-18":"2024-06-21"]) #查看2024年6月18日到2024年6月21日的数据
#任务三
import pandas as pd
price_SHSC = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/1726305586753.xlsx") #导入数据
price_SHSC["日期"] = pd.to_datetime(price_SHSC["日期"] , format='%Y%m%d') # 转换为日期时间格式
price_SHSC.set_index("日期",inplace=True) # 将日期列设为price_SHSC数据框的索引
print(price_SHSC.iloc[15:28,1:4]) #截取第16行至18行、第二列至第四列
#任务四
import pandas as pd
price_SHSC = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/20220819/xlsx/1560448223221932032.xlsx") #导入数据
price_SHSC["日期"] = pd.to_datetime(price_SHSC["日期"] , format='%Y%m%d') # 转换为日期时间格式
price_SHSC.set_index("日期",inplace=True) # 将日期列设为price_SHSC数据框的索引
print(price_SHSC[price_SHSC["白云机场"]>=10]) #截取白云机场收盘价大于等于17.6元对应的子数据框
# 输出华能国际
print(price_SHSC[(price_SHSC["华能国际"]<9.5)&(price_SHSC["南方航空"]>5.5)&(price_SHSC["三一重工"]<16)]) 运行后核对:核对 price_SHSC 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。
本地纠错(平台原注释保持不变):iloc[15:28, 1:4] 实际选取位置 15–27,即按自然序号为第 16–28 行,而不是“第16行至18行”;price_SHSC["白云机场"] >= 10 的实际阈值是 10 元,而不是注释中的 17.6 元。
拓展练习:把输入表替换为本地中国上市公司数据的同结构子集;指出必须保持的字段、数据类型和质量检查。
| 操作类型 | 方法 | 关键特性 |
|---|---|---|
| 列选择 | df['列名'] / df[['列1','列2']] |
单列返回Series,多列返回DataFrame |
| 标签索引 | df.loc[行标签, 列标签] |
闭区间切片,语义清晰 |
| 位置索引 | df.iloc[行位置, 列位置] |
半开区间切片,算法友好 |
| 布尔筛选 | df[条件] |
&/\|/~组合,括号必须 |
| 成员检查 | df['列'].isin(列表) |
单次哈希探测通常均摊常数级,整列仍需遍历 |
| 简洁查询 | df.query('条件表达式') |
类SQL语法,复杂条件可读性好 |
lst-loc-indexing、lst-query-method 与布尔筛选,生成同一候选集。code:str, area:str, pe:float, authorized:bool;只允许授权且有限的记录进入候选。import pandas as pd, numpy as np # 导入表格工具以构造、对齐和核对数据
frame=pd.DataFrame({'code':['A','B','C','D'],'area':['上海','浙江','江苏','北京'],'pe':[15,35,np.nan,20],'authorized':[True,True,True,False]}) # 构造带业务字段的数据框输入
mask=frame['area'].isin(['上海','浙江','江苏']) & frame['pe'].between(0,30) & frame['authorized'] # 构造带业务字段的数据框输入
candidate=frame.loc[mask,['code','area','pe']].copy() # 构造带业务字段的数据框输入
manual=[row.code for row in frame.itertuples() if row.area in {'上海','浙江','江苏'} and pd.notna(row.pe) and 0<=row.pe<=30 and row.authorized] # 用逐项公式独立复算向量结果
assert candidate['code'].tolist()==manual # 用逐项公式独立复算向量结果
print(mask.tolist(),candidate) # 按标签与条件筛出候选记录and 代替 &;缺括号;链式赋值;把哈希单次探测概括成整列 O(1)。[商业大数据分析与应用]